package com.leyou.search.service;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import com.leyou.common.pojo.PageResult;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SpuDTO;
import com.leyou.item.entity.*;
import com.leyou.search.repository.SearchRepository;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.utils.HighlightUtils;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private SearchRepository searchRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private ItemClient itemClient;


    /**
     * 将数据源【数据库】的数据转成索引库的文档对象
     * 其本质：是将数据库中一个Spu对象转成一个索引库中的Goods对象
     */
    public Goods buildGoods(SpuDTO spuDTO){

        //第四步：根据Spu的id查询Sku集合【封装skus，price，all字段】
        List<Sku> skus = itemClient.findSkusBySpuId(spuDTO.getId());
        //由于索引库中无需存放Sku全部信息，那么我们需要定义一个map来存放所需要的信息
        List<Map<String, Object>> skuList = new ArrayList<>();
        skus.forEach(sku -> {
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            skuMap.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            skuMap.put("price", sku.getPrice());
            skuMap.put("title", sku.getTitle().substring(spuDTO.getName().length()));//SpuName和SkuName除去SpuName之外的部分拼接
            skuList.add(skuMap);
        });
        //收集所有sku价格的set集合
        Set<Long> prices = skus.stream().map(Sku::getPrice).collect(Collectors.toSet());
        //拼接all字段
        String all = spuDTO.getCategoryName()
                +spuDTO.getBrandName()
                +skus.stream().map(Sku::getTitle).collect(Collectors.joining());

        //第五步：根据第三级分类id查询参与搜索的规格参数【Specs字段的key】
        List<SpecParam> specParams = itemClient.findSpecParams(null, spuDTO.getCid3(), true);

        //第六步：根据Spu的id查询SpuDetial对象【Specs字段的value】
        SpuDetail spuDetail = itemClient.findSpuDetailById(spuDTO.getId());

        //获取通用规格参数值所在的字段
        String genericSpecStr = spuDetail.getGenericSpec();
        //将字符串格式通用规格参数转成Map对象
        Map<Long, Object> genericSpecObj = JsonUtils.toMap(genericSpecStr, Long.class, Object.class);
        //获取特有规格参数值所在的字段
        String specialSpecStr = spuDetail.getSpecialSpec();
        //将字符串个数的特有规格参数转成Map对象
        Map<Long, List<Object>> specialSpecObj = JsonUtils.nativeRead(specialSpecStr, new TypeReference<Map<Long, List<Object>>>() {});

        //创建一个存储动态过滤条件的map
        Map<String, Object> specs = new HashMap<>();
        //specs这个Map中键值对的数量取决于key所在的specParams集合的长度
        specParams.forEach(specParam -> {
            String key = specParam.getName();//key值就是规格参数的名称
            Object value = null;//值有两个来源，genericSpecObj和specialSpecObj
            //判断当前规格参数是否为通用规格参数
            if(specParam.getGeneric()){
                //value的值来自于genericSpecObj
                value = genericSpecObj.get(specParam.getId());
            }else {
                //value的值来自于specialSpecObj
                value = specialSpecObj.get(specParam.getId());
            }

            //对所有数字类型的过滤条件进行处理，兑换成区间存入索引库
            if(specParam.getNumeric()){
                value = chooseSegment(value, specParam);
            }
            //给specs赋值
            specs.put(key, value);
        });

        //第一步：构建一个Goods对象
        Goods goods = new Goods();
        //第二步：先给可以直接通过Spu对象来赋值的属性赋值
        goods.setId(spuDTO.getId());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setSpuName(spuDTO.getName());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setSubTitle(spuDTO.getSubTitle());
        //第三步：把剩余的属性都列出来备用
        goods.setSkus(JsonUtils.toString(skuList));
        goods.setPrice(prices);
        goods.setAll(all);
        goods.setSpecs(specs);
        return goods;

    }


    private String chooseSegment(Object value, SpecParam p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }


    /**
     * 提供数据渲染搜索页
     */
    public Map<String, Object> loadSearchPageData(SearchRequest request) {
        //获取数据分页信息
        PageResult<GoodsDTO> pageResult = itemPageQuery(request);
        //获取数据聚合结果
        Map<String, List<?>> filterConditionsMap = aggQuery(request);
        //封装结果map
        Map<String, Object> result = new HashMap<>();
        result.put("itemsList", pageResult.getItems());
        result.put("totalCount", pageResult.getTotal());
        result.put("totalPages", pageResult.getTotalPage());
        result.put("filterConditions", filterConditionsMap);
        return result;
    }


    /**
     * <<<<<<<<<<<<<<<<<<<<<<<<<聚合查询
     */
    private Map<String, List<?>> aggQuery(SearchRequest request) {

        //提供有序结果map
        Map<String , List<?>> resultMap = new LinkedHashMap<>();
        //提供封装复杂查询条件的对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //设置查询条件
        nativeSearchQueryBuilder.withQuery(createQueryBuilder(request));
        //设置分页条件
        nativeSearchQueryBuilder.withPageable(PageRequest.of(request.getPage(),request.getSize()));
        //设置查询字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null ));

        //定义分类聚合结果的名称
        String categoryAggName = "categoryAgg";
        //设置分类聚合条件
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("categoryId"));

        //定义品牌聚合结果的名称
        String brandAggName = "brandAgg";
        //设置品牌聚合条件
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));


        //执行索引库查询
        AggregatedPage<Goods> goodsPageQuery = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
        //得到所有聚合结果
        Aggregations aggregations = goodsPageQuery.getAggregations();

        //获取分类聚合结果
        Terms categoryAggResult = aggregations.get(categoryAggName);
        //处理封装分类聚合结果
        List<Long> cids = handlerCategoryAggResult(resultMap, categoryAggResult);

        //获取品牌聚合结果
        Terms brandAggResult = aggregations.get(brandAggName);
        //处理封装品牌聚合结果
        List<Long> brands = handlerBrandAggResult(resultMap, brandAggResult);
        //通过分类id的集合封装规格参数过滤条件
        handlerSpecParamFilterAggResult(resultMap, request, cids);
        return resultMap;
    }


    /**
     * <<<<<<<<<<<<<<<<<<<<<通过分类id的集合封装规格参数过滤条件
     */
    private void handlerSpecParamFilterAggResult(Map<String, List<?>> resultMap, SearchRequest request, List<Long> cids) {
        cids.forEach(cid->{
            //提供封装复杂查询条件的对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //设置查询条件
            nativeSearchQueryBuilder.withQuery(createQueryBuilder(request));
            //设置分页条件
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
            //设置查询字段
            nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));

            //根据当前分类id查询出该分类下参与搜索的规格参数
            List<SpecParam> specParams = itemClient.findSpecParams(null, cid, true);

            //循环规格参数添加聚合条件
            specParams.forEach(specParam -> {
                //定义聚合结果的名称
                String aggName = specParam.getName();
                //获取当前规格参数在索引库中对应的域名
                String filedName = "specs."+specParam.getName()+".keyword";
                //添加聚合条件
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(aggName).field(filedName));
            });

            //执行聚合查询
            AggregatedPage<Goods> goodsAgg = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);

            //得到所有的聚合结果
            Aggregations aggregations = goodsAgg.getAggregations();
            //循环取出规格参数过滤条件并封装到结果map中
            specParams.forEach(specParam -> {
                //得到聚合结果的名称
                String aggName = specParam.getName();
                //通过聚合名称获取到聚合结果
                Terms specParamTerm = aggregations.get(aggName);
                //解析聚合结果并放入结果map
                List<String> specParamsFilterParamList = specParamTerm.getBuckets()
                        .stream()
                        .map(Terms.Bucket::getKeyAsString)
                        .collect(Collectors.toList());
                //存入filterParamsMap中
                resultMap.put(aggName, specParamsFilterParamList);
            });

        });

    }


    /**
     * <<<<<<<<<<<<<<<<<<<<<<<处理封装品牌聚合结果
     */
    private List<Long> handlerBrandAggResult(Map<String, List<?>> resultMap, Terms brandAggResult) {
        //获取品牌id的集合
        List<Long> bids = brandAggResult.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //通过品牌id的集合查询品牌对象的集合
        List<Brand> brands = itemClient.findBrandsByIds(bids);
        //将品牌对象集合放入结果map
        resultMap.put("品牌", brands);
        return bids;
    }


    /**
     * <<<<<<<<<<<<<<<<<<<<<处理封装分类聚合结果
     */
    private List<Long> handlerCategoryAggResult(Map<String, List<?>> resultMap, Terms categoryAggResult) {

        //获取分类id的集合
        List<Long> cids = categoryAggResult.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //通过分类id的集合查询分类对象的集合
        List<Category> categories = itemClient.findCategorysByIds(cids);
        //将分类对象集合放入结果map
        resultMap.put("分类", categories);
        return cids;
    }

    /**
     * <<<<<<<<<<<<<<<<<<<<<<<<<<<获取数据分页信息
     * @param request
     * @return
     */
    private PageResult<GoodsDTO> itemPageQuery(SearchRequest request) {
        //创建一个可以封装复杂查询条件的查询构建器
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //设置查询条件
        nativeSearchQueryBuilder.withQuery(createQueryBuilder(request));
        //设置分页条件
        nativeSearchQueryBuilder.withPageable(PageRequest.of(request.getPage()-1,request.getSize()));
        //设置查询字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","spuName","subTitle","skus",}, null));
        //设置高亮字段
        HighlightUtils.highlightField(nativeSearchQueryBuilder,"spuName");

        //添加排序条件
        String sortBy = request.getSortBy();
        Boolean desc = request.getDescending();

        //判断是否有排序条件
        if (StringUtils.isNotBlank(sortBy)){
            //如果不为空,则进行排序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc ? SortOrder.DESC : SortOrder.ASC));
        }


        //执行索引库查询
        AggregatedPage<Goods> esPageResult = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class,HighlightUtils.highlightBody(Goods.class,"spuName"));
        //获取指定页数据列表
        List<Goods> goodsList = esPageResult.getContent();
        //判空
        if (CollectionUtils.isEmpty(goodsList)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //讲Goods集合转成GoodsDTO集合
        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        //封装自定义分页对象
        PageResult<GoodsDTO> result = new PageResult<>(esPageResult.getTotalElements(),Long.valueOf(esPageResult.getTotalPages()),goodsDTOS);
        //返回结果
        return result;
    }


    /**
     * <<<<<<<<<<<<<<<<<<<<<<<<<查询条件封装，多个词之间是and关系
     */
    public QueryBuilder createQueryBuilder(SearchRequest request) {

        //提供一个过滤查询的Bool对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //封装查询条件
        boolQuery.must(QueryBuilders.multiMatchQuery(request.getKey(),"spuName","all").operator(Operator.AND));
        //获取到要封装的过滤条件参数
        Map<String , Object> filterParams = request.getFilterParams();
        if (!CollectionUtils.isEmpty(filterParams)){
            //遍历
            filterParams.entrySet().forEach(entry->{

                //获取key
                String key = entry.getKey();
                //对Key进行处理
                if (StringUtils.equals(key,"分类")){
                    key = "categoryId";
                }else if (!StringUtils.equals(key,"brandId")){
                    key = "specs." + key + ".keyword";
                }

                //获取value
                Object value = entry.getValue();
                //封装过滤条件
                boolQuery.filter(QueryBuilders.termQuery(key,value));

            });
        }
        return boolQuery;
        //        return QueryBuilders.multiMatchQuery(request.getKey(), "spuName", "all").operator(Operator.AND);
    }


    /**
     * 切换页码
     */
    public List<GoodsDTO> goodPageQuery(SearchRequest request) {

        PageResult<GoodsDTO> pageResult = itemPageQuery(request);
        return pageResult.getItems();
    }

    /**
     * 添加上架商品的索引
     * @param id
     */
    public void addIndex(Long id) {
        SpuDTO spuDTO = itemClient.findSpuDtoById(id);
        Goods goods = buildGoods(spuDTO);
        searchRepository.save(goods);
    }

    /**
     * 删除下架商品的索引
     * @param id
     */
    public void deleteIndex(Long id) {
        searchRepository.deleteById(id);
    }
}
